using System.Diagnostics;
using System.ServiceModel;
using Microsoft.Vsts.Ranger.Migration.TfsToTfs.Service.Business;
using Microsoft.Vsts.Ranger.Migration.TfsToTfs.Service.DataContracts;
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Configuration;
using Microsoft.Vsts.Ranger.Migration.TfsToTfs.Service.ServiceContracts;
using Microsoft.TeamFoundation.Migration.Toolkit;
using Microsoft.Vsts.Ranger.Migration.TfsToTfs.Service.Schemas;
using Microsoft.Vsts.Ranger.Migration.TfsToTfs.Service.Data;
using System.Collections;
using System.Threading;
using System.Reflection;

namespace Microsoft.Vsts.Ranger.Migration.TfsToTfs.Service.Services
{
    //Dont run the service on the UI thread when invoked from the SmartClient
    [System.ServiceModel.ServiceBehavior(/*UseSynchronizationContext=false, InstanceContextMode = InstanceContextMode.Single*/)]
    public class MigrationService : IMigrationServiceContract
    {
        private static EventLog log;

        private MigrationConfiguration _config;
        private Dictionary<string, TfsToTfsSession> _mirroredSessions = new Dictionary<string, TfsToTfsSession>();
        private bool _pollingActive;
        private EventData _eventData = new EventData();
        

        static MigrationService()
        {
            log = new EventLog();
            log.Source = "Application";
        }
   

        private bool PollingActive
        {
            get { return _pollingActive;  }
            set { _pollingActive = value; }
        }

        private MigrationConfiguration Config
        {
            get 
            {
                return _config;
            }
            set 
            {
                _config = value;
            }
        }


        private void AddMirroredSession(TfsToTfsSession session)
        {
            lock (_mirroredSessions)
            {
                _mirroredSessions.Add(session.Session.Id, session);
            }
        }

        private void RemoveMirroredSession(string sessionId)
        {
            lock (_mirroredSessions)
            {
                _mirroredSessions.Remove(sessionId);
            }
        }


        private string ReadConfigFileName()
        {
            string fileName = System.Configuration.ConfigurationManager.AppSettings["MigrationConfigFile"];

            if (fileName == null)
            {
                log.WriteEntry("The configuration file is missing the 'MigrationConfigFile' path.",
                               EventLogEntryType.Error, 0, 0);
                return null;
            }

            fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
            return fileName;
        }

        public string GetConfigFileContents()
        {
            try
            {
                string configFileName = ReadConfigFileName();

                if(File.Exists(configFileName))
                    return File.ReadAllText(Path.Combine(GetApplicationPath(), configFileName));
                else
                    throw new FaultException<ServerError>(new ServerError("Wrong configuration file name."), "The configuration file was not found:"+ configFileName );
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);
                throw new FaultException<ServerError>(new ServerError("An error ocurred trying to read the Migration Configuration file"), ex.ToString());
            }
        }


        public string GetTemplateFileContents()
        {
            try
            {
                string configFileName =
                    System.Configuration.ConfigurationManager.AppSettings["MigrationConfigTemplateFile"];

                if (configFileName == null)
                {
                    log.WriteEntry("The configuration file is missing the 'MigrationConfigFile' path.",
                                   EventLogEntryType.Error, 0, 0);
                    return null;
                }

                return File.ReadAllText(Path.Combine(GetApplicationPath(), configFileName));
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);
                throw new FaultException<ServerError>(new ServerError("An error ocurred trying to read the Migration Configuration Template file" +
                                         ex.ToString()));
            }
        }

        private int GetSleepInterval()
        {
            try
            {
                string sleepIntervalString =
                    System.Configuration.ConfigurationManager.AppSettings["SleepIntervalInSeconds"];

                return int.Parse(sleepIntervalString);
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);
                throw new FaultException<ServerError>(new ServerError("Sleep Interval Configuration Setting (SleepIntervalInSeconds) could not be retreived from config.  " +
                                         ex.ToString()));
            }
        }
    
        private string GetApplicationPath()
        {
            return Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName;
        }

        public void SaveConfigFile(string fileContents)
        {
            try
            {
                string configFileName = ReadConfigFileName();

                if (File.Exists(configFileName)) 
                    File.Delete(configFileName);

                File.WriteAllText(configFileName, fileContents);
                //InitializeService();
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);
                throw new FaultException<ServerError>(new ServerError("An error ocurred trying to save the Migration Configuration Template file" +
                                         ex.ToString()));
            }
        }



        public void InitializeService()
        {
            LoadToolkitConfiguration();
            
            PollingActive = true;

            //Restart any sessions that should be running
            System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(ReStartSessions));

        }

        private void LoadToolkitConfiguration()
        {
            Trace.AutoFlush = true;

            string configFileName = ReadConfigFileName();

            try
            {
                using (FileStream fs = File.Open(configFileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    Config = MigrationConfiguration.Load(fs);
                }

                //Create the Schema if it doesn't exist yet
                DataAccessManager.Current.CreateSchema(false, DataAccessManager.SchemaType.All);
            }
            catch (IOException ioe)
            {

                log.WriteEntry(ioe.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                    new ServerError("Unable to load configuration file due to the following failure: " + ioe.ToString()));

            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                   new ServerError(string.Format("Unable to create DB Schema: {0}", ex.Message)));
            }

        }

        public void ShutDownService()
        {
            try
            {
                foreach (TfsToTfsSession session in _mirroredSessions.Values)
                {
                    if (session != null && session.Session != null && session.Session.IsRunning)
                        session.Session.Stop();
                }

                PollingActive = false;
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                   new ServerError(string.Format("Error shutting down service: {0}", ex.Message)));
            }
        }

        public void StartSession(string sessionId, bool oneWay)
        {
            try
            {
                EventData data = new EventData();

                data.InsertSessionInstanceRow(sessionId, true, oneWay);
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                   new ServerError(string.Format("Error starting session: {0}", ex.Message)));
            }
        }

        private void StartSession(string sessionId, bool oneWay, bool storeDbRow, bool oneTimeOnly, int? sessionInstanceId)
        {
            TfsToTfsSession tfsToTfsSession = FindExistingSession(sessionId);
            if (tfsToTfsSession == null)
            {
                LoadToolkitConfiguration();
                tfsToTfsSession = FindSession(sessionId);
            }
            if(tfsToTfsSession != null)
            {
                //If the session is already running, dont restart it
                if (tfsToTfsSession.Session.IsRunning)
                    return;

                if (storeDbRow)
                {
                    tfsToTfsSession.SessionEventManager.StoreSessionInstance(oneTimeOnly, oneWay);
                }
                else
                {
                    if (sessionInstanceId.HasValue)
                    {
                        tfsToTfsSession.SessionEventManager.SessionInstanceId = sessionInstanceId.Value;
                    }
                    else
                    {
                        throw new MigrationException("Cannot start session.  Session instance ID has no value to Log events.");
                    }
                }

                //It's a valid session.  Start the session...
                if (oneWay)
                {
                    System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(startSyncronize), tfsToTfsSession.Session);
                }
                else
                {
                    System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(startSyncronizeFull), tfsToTfsSession.Session);
                }
            }

        }

        private void startSyncronize(object state)
        {
            try
            {
                IMigrationSession session = (IMigrationSession)state;

                session.Synchronize(SystemType.Tfs);
            }
            catch (Exception ex)
            {
                //Background thread, dont throw if error occurs...
                log.WriteEntry("Error occurred starting the Migration (one way).  Error Details: " + ex.ToString(), EventLogEntryType.Error);
            }
        }

        private void startSyncronizeFull(object state)
        {
            try
            {
                IMigrationSession session = (IMigrationSession)state;

                session.SynchronizeFull();
            }
            catch (Exception ex)
            {
                //Background thread, dont throw if error occurs...
                log.WriteEntry("Error occurred starting the Migration (both ways).  Error Details: " + ex.ToString(), EventLogEntryType.Error);
            }
        }

        public void StopSession(string sessionId, int sessionInstanceId)
        {
            try
            {
                //Mark the record as stopped first (even if stopping the session fails)
                _eventData.UpdateSessionInstance(sessionInstanceId, "Stopped");

                //WCF is no longer used for remoting, so it can no longer bind to the running instance.
                //if (this._mirroredSessions.ContainsKey(sessionId))
                //{
                //    IMigrationSession session = _mirroredSessions[sessionId].Session;

                //    if(session.IsRunning)
                //        session.Stop();

                //    _mirroredSessions.Remove(sessionId);
                //}

            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                   new ServerError(string.Format("Error stopping session: {0}", ex.Message)));
            }
        }
        public void StartMirror(string sessionId, bool oneWay)
        {
            try
            {

                EventData data = new EventData();

                data.InsertSessionInstanceRow(sessionId, false, oneWay);

            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                   new ServerError(string.Format("Error starting mirror: {0}", ex.Message)));
            }
        }

        [Obsolete]
        private void StartMirror(string sessionId, bool oneWay, bool storeDbRow, int? sessionInstanceId)
        {
            //Check to see if it's already running
            if (_mirroredSessions.ContainsKey(sessionId))
            {
                TfsToTfsSession session = _mirroredSessions[sessionId];

                if (!session.Session.IsRunning)
                    session.Session.Start();
            }
            else
            {

                IMigrationSession session = this.FindSession(sessionId).Session;


                if (session != null)
                {
                    TfsToTfsSession tfsSession = new TfsToTfsSession();

                    tfsSession.Session = session;
                    tfsSession.SessionEventManager = new SessionEventManager();

                    tfsSession.SessionEventManager.AttachEventHandlers(session);

                    if (storeDbRow)
                    {
                        tfsSession.SessionEventManager.StoreSessionInstance(false, oneWay);
                    }
                    else
                    {
                        tfsSession.SessionEventManager.SessionInstanceId = sessionInstanceId.Value;
                    }

                    _mirroredSessions.Add(session.Id, tfsSession);

                    session.Start();
                }
                else
                {
                    throw new MigrationException("Session not found in configured collection.  It cannot be started");
                }
            }
        }

        public void StopMirror(string sessionId, int sessionInstanceId)
        {
             StopSession(sessionId, sessionInstanceId);
        }


        public SessionsDataset GetRunningSessions()
        {
            try
            {
                SessionsDataset sessionsDs = new SessionsDataset();

                foreach (VersionControlSession session in Config.VC.Sessions.Values)
                {
                    if (session.IsRunning)
                        sessionsDs.RunningSessions.AddRunningSessionsRow(session.Id);
                }

                foreach (WorkItemTrackingSession session in Config.Wit.Sessions.Values)
                {
                    if (session.IsRunning)
                        sessionsDs.RunningSessions.AddRunningSessionsRow(session.Id);
                }

                sessionsDs.AcceptChanges();

                return sessionsDs;
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                   new ServerError(string.Format("Error getting running sessions: {0}", ex.Message)));
            }
        }

        public SessionsDataset GetSessionInstances(bool includeCompleted)
        {
            SessionsDataset ds = new SessionsDataset();
            try
            {
                _eventData.GetSessionInstances(ds, includeCompleted);
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                   new ServerError(string.Format("Error getting session instances: {0}", ex.Message)));
            }

            return ds;
        }


        public SessionsDataset GetEventsForSessionInstance(int sessionInstanceId)
        {
            SessionsDataset ds = new SessionsDataset();

            try
            {
                _eventData.GetSessionInstanceEvents(ds, sessionInstanceId);
            }
            catch (Exception ex)
            {
                log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

                throw new FaultException<ServerError>(
                   new ServerError(string.Format("Error getting events for session instance: {0}", ex.Message)));
            }

            return ds;

        }

        private void ReStartSessions(object state)
        {
            while (PollingActive)
            {
                try
                {
                    //Get the sessions that should be running from the database
                    SessionsDataset ds = new SessionsDataset();

                    _eventData.GetSessionInstances(ds, false);

                    //Restart them if necessary
                    foreach (SessionsDataset.SessionInstancesRow row in ds.SessionInstances)
                    {
                        StartSession(row.SessionID, row.OneWayMigration, false, true, row.SessionInstanceID);
                    }

                    System.Threading.Thread.Sleep(GetSleepInterval() * 1000);
                }
                catch (Exception ex)
                {
                    log.WriteEntry("Error Starting Sessions.  Resuming...  Error Details: " +  ex.ToString(), EventLogEntryType.Error);
                }
            }
        }

        private TfsToTfsSession FindExistingSession(string sessionId)
        {
            if (this._mirroredSessions.ContainsKey(sessionId))
            {
                return _mirroredSessions[sessionId];
            }
            return null;
        }

        //---Dont need this anymore since ongoing sync is now built into the toolkit
        //private void StartPolling(object state)
        //{
        //    //Background thread...  Catch any errors and log to the event log
        //    try
        //    {
        //        while (PollingActive)
        //        {
        //            foreach (IMigrationSession session in this._mirroredSessions)
        //            {
        //                if(!session.IsRunning) 
        //                    session.Start();
        //            }

        //            //Sleep for a few seconds between loops
        //            System.Threading.Thread.Sleep(5000);
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        System.Diagnostics.EventLog.WriteEntry("TfsMigrationService", ex.Message + " " + ex.StackTrace);
        //    }
            
        //}


        private TfsToTfsSession FindSession(string sessionId)
        {

            if (this._mirroredSessions.ContainsKey(sessionId))
            {
                return _mirroredSessions[sessionId];
            }
            else
            {
                IMigrationSession session = null;

                //Need to find the VC or WIT Session, look in VC first...
                if (Config.VC != null && Config.VC.Sessions.Count > 0 && Config.VC.Sessions.ContainsKey(sessionId))
                {
                    session = Config.VC.Sessions[sessionId];
                }

                //If it's not found yet, look in WIT sessions
                if (session == null && Config.Wit != null && Config.Wit.Sessions.Count > 0 && Config.Wit.Sessions.ContainsKey(sessionId))
                    session = Config.Wit.Sessions[sessionId];

                //If it's still not found, throw an exception, if it is, start the session.
                if (session == null)
                {
                    throw new MigrationException("Session " + sessionId + " could not be located.  Please check the configuration file to insure that this session exists.");
                }
                else
                {
                    //New up the session and store it for next time
                    TfsToTfsSession tfsSession = new TfsToTfsSession();

                    tfsSession.Session = session;
                    tfsSession.SessionEventManager = new SessionEventManager();

                    tfsSession.SessionEventManager.AttachEventHandlers(session);

                    AddMirroredSession(tfsSession);

                    return tfsSession;
                }
            }
        }

        

    }
}
